/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-07-28     MXH        the first version
 */
#include "board.h"
#include "rtconfig.h"

#ifdef BSP_USING_FLASH
#include "drv_flash.h"

#if defined(RT_USING_FAL)
#include "fal.h"
#endif

#define DRV_DEBUG
#define LOG_TAG                "drv.flash"
#include <drv_log.h>


#define CH32_FLASH_PAGE_SIZE        4096
/* use all non-zero wait flash */
#define CH32_FLASH_START_ADDR       (0x08048000UL)
#define CH32_FLASH_END_ADDR         (0x08078000UL)


/**
  * @brief  Gets the page of a given address
  * @param  Addr: Address of the FLASH Memory
  * @retval The page of a given address
  */
static uint32_t GetPage(uint32_t addr)
{
    uint32_t page = 0;

    page = RT_ALIGN_DOWN(addr, CH32_FLASH_PAGE_SIZE);
    return page;
}

/**
 * Read data from flash.
 * @note This operation's units is word.
 *
 * @param addr flash address
 * @param buf buffer to store read data
 * @param size read bytes size
 *
 * @return result
 */
int ch32_flash_read(rt_uint32_t addr, rt_uint8_t *buf, size_t size)
{
    size_t i;

    if ((addr + size) > CH32_FLASH_END_ADDR)
    {
        LOG_E("read outrange flash size! addr is (0x%p)", (void*)(addr + size));
        return -RT_EINVAL;
    }

    for (i = 0; i < size; i++, buf++, addr++)
    {
        *buf = *(rt_uint8_t *) addr;
    }


    return size;
}

/**
 * Write data to flash.
 * @note This operation's units is word.
 * @note This operation must after erase. @see flash_erase.
 *
 * @param addr flash address
 * @param buf the write data buffer
 * @param size write bytes size
 *
 * @return result
 */
int ch32_flash_write(rt_uint32_t addr, const uint8_t *buf, size_t size)
{
    rt_err_t result = RT_EOK;
    rt_uint32_t end_addr = addr + size;

    if (end_addr > CH32_FLASH_END_ADDR)
    {
        LOG_E("ERROR: write outrange flash size! addr is (0x%p)\n", (void*)(addr + size));
        return -RT_EINVAL;
    }

    if(addr % 4 != 0)
    {
        LOG_E("write addr must be 4-byte alignment");
        return -RT_EINVAL;
    }

    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR);

    while(addr < end_addr)
    {
        if (FLASH_ProgramWord(addr, *((rt_uint32_t *)buf)) == FLASH_COMPLETE)
        {
            if (*(rt_uint32_t *)addr != *(rt_uint32_t *)buf)
            {
                result = -RT_ERROR;
                break;
            }
            addr += 4;
            buf  += 4;
        }
        else
        {
            result = -RT_ERROR;
            break;
        }
        FLASH_WaitForLastOperation(0x00005000);
    }

    FLASH_Lock();
    if (result != RT_EOK)
    {
        return result;
    }

    return size;
}

/**
 * Erase data on flash.
 * @note This operation is irreversible.
 * @note This operation's units is different which on many chips.
 *
 * @param addr flash address
 * @param size erase bytes size
 *
 * @return result
 */
int ch32_flash_erase(rt_uint32_t addr, rt_size_t size)
{
    uint32_t FirstPage = 0, NbOfPages = 0, Page_Num = 0;
    rt_uint32_t end_addr = addr + size;

    if (end_addr > CH32_FLASH_END_ADDR)
    {
        LOG_E("ERROR: write outrange flash size! addr is (0x%p)\n", (void*)(addr + size));
        return -RT_EINVAL;
    }

    /* Unlock the Flash to enable the flash control register access */
    FLASH_Unlock();

    FirstPage = GetPage(addr);
    NbOfPages = (size + CH32_FLASH_PAGE_SIZE - 1)/  CH32_FLASH_PAGE_SIZE;

    for(; Page_Num < NbOfPages; Page_Num++)
    {
        FLASH_ErasePage(FirstPage + Page_Num * CH32_FLASH_PAGE_SIZE);
        FLASH_WaitForLastOperation(0x000B0000);
    }

    FLASH_Lock();

    LOG_D("erase done: addr (0x%p), size %d", (void*)addr, size);
    return size;
}

#if defined(RT_USING_FAL)

static int init(void);
static int read(long offset, rt_uint8_t *buf, size_t size);
static int write(long offset, const rt_uint8_t *buf, size_t size);
static int erase(long offset, size_t size);

const struct fal_flash_dev ch32_onchip_flash = {
        ON_CHIP_FLASH_NAME,
        CH32_FLASH_START_ADDR,
        192 * 1024, 4096,
        {init, read, write, erase},
        .write_gran = 32,
};

static int init(void)
{
    /* Flash Clock <= 60MHz */
    if(SystemCoreClock >= 60000000UL)
    {
        FLASH_Access_Clock_Cfg(FLASH_Access_SYSTEM_HALF);
    }
    else
    {
        FLASH_Access_Clock_Cfg(FLASH_Access_SYSTEM);
    }

    return RT_EOK;
}

static int read(long offset, rt_uint8_t *buf, size_t size)
{
    return ch32_flash_read(ch32_onchip_flash.addr + offset, buf, size);
}

static int write(long offset, const rt_uint8_t *buf, size_t size)
{
    return ch32_flash_write(ch32_onchip_flash.addr + offset, buf, size);
}

static int erase(long offset, size_t size)
{
    return ch32_flash_erase(ch32_onchip_flash.addr + offset, size);
}

#endif
#endif /* BSP_USING_ON_CHIP_FLASH */
